﻿namespace SuperModel.Generation
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BehaviorModel;
    using Castle.Core.Interceptor;
    using Castle.DynamicProxy;
    using Extensions;

    /// <summary>
    /// The model proxier
    /// </summary>
    public class ModelProxier
    {
        /// <summary>
        /// The Dynamic Proxy Generator
        /// </summary>
        private readonly ProxyGenerator proxyGenerator = new ProxyGenerator();

        /// <summary>
        /// The Filter Attribute Chain Cache
        /// </summary>
        private static readonly Dictionary<Type, AttributeChainOnType> FilterAttributesChainCache = new Dictionary<Type, AttributeChainOnType>();

        /// <summary>
        /// Proxies the new instance of a type.
        /// </summary>
        /// <param name="typeToBuild">The type to build.</param>
        /// <param name="args">The type's constructor arguments</param>
        /// <returns>
        /// An proxied instance of the requested type
        /// </returns>
        public object ProxyNewInstanceOf(Type typeToBuild, object[] args)
        {
            var interceptors = CreateInterceptors(typeToBuild);
            var returnMe = proxyGenerator.CreateClassProxy(typeToBuild, interceptors.ToArray(), args);
            return returnMe;
        }

        /// <summary>
        /// Creates a filter chain
        /// </summary>
        /// <param name="type">
        /// The target type.
        /// </param>
        /// <typeparam name="TFilter">
        /// The filter type.
        /// </typeparam>
        /// <typeparam name="TAttribute">
        /// The attribute type.
        /// </typeparam>
        /// <returns>
        /// The list of filters.
        /// </returns>
        internal static List<TFilter> CreateFilterChain<TFilter, TAttribute>(Type type)
            where TFilter : class
            where TAttribute : PropertyInterceptionAttributeBase
        {
            var chain = new List<TFilter>();

            foreach (var attributeMap in GetClassAttributesChain<TAttribute>(type))
            {
                foreach (var attribute in attributeMap.Value)
                {
                    foreach (Type commandType in attribute.FilterTypes)
                    {
                        var command = (IPropertyInterceptor)Activator.CreateInstance(commandType);
                        command.PropertyName = attributeMap.Key;
                        chain.Add(command as TFilter);
                    }
                }
            }

            return chain;
        }

        /// <summary>
        /// Creates Interceptors for a Type
        /// </summary>
        /// <param name="typeToIntercept">
        /// The type to intercept.
        /// </param>
        /// <returns>
        /// The type interceptor.
        /// </returns>
        private static IList<IInterceptor> CreateInterceptors(Type typeToIntercept)
        {
            // Create the Property Set Interceptor
            var interceptor = new PropertyInterceptor();
            List<IPropertySetFilter> beforeSetFilterChain = CreateFilterChain<IPropertySetFilter, BeforePropertySetAttribute>(typeToIntercept);
            List<IPropertySetFilter> afterSetFilterChain = CreateFilterChain<IPropertySetFilter, AfterPropertySetAttribute>(typeToIntercept);
            beforeSetFilterChain.ForEach(interceptor.AddBefore);
            afterSetFilterChain.ForEach(interceptor.AddAfter);

            // Create the Property Get Interceptor
            List<IPropertyGetAction> beforeGetFilterChain = CreateFilterChain<IPropertyGetAction, BeforePropertyGetAttribute>(typeToIntercept);
            List<IPropertyGetAction> afterGetFilterChain = CreateFilterChain<IPropertyGetAction, AfterPropertyGetAttribute>(typeToIntercept);
            beforeGetFilterChain.ForEach(interceptor.AddBefore);
            afterGetFilterChain.ForEach(interceptor.AddAfter);

            return new List<IInterceptor>(new IInterceptor[] { interceptor });
        }

        /// <summary>
        /// Gets a Class Attribute Chain
        /// </summary>
        /// <param name="typeToIntercept">
        /// The type to intercept.
        /// </param>
        /// <typeparam name="TAttribute">
        /// The attribute type.
        /// </typeparam>
        /// <returns>
        /// The attribute chain.
        /// </returns>
        private static IDictionary<string, IList<TAttribute>> GetClassAttributesChain<TAttribute>(Type typeToIntercept)
            where TAttribute : PropertyInterceptionAttributeBase
        {
            // Create the attribute chain map if necessary
            if (!FilterAttributesChainCache.ContainsKey(typeof(TAttribute)))
            {
                FilterAttributesChainCache[typeof(TAttribute)] = new AttributeChainOnType();
            }

            // Build the attribute chain if necessary
            if (!FilterAttributesChainCache[typeof(TAttribute)].ContainsKey(typeToIntercept))
            {
                FilterAttributesChainCache[typeof(TAttribute)].Add(typeToIntercept, BuildAttributeChain<TAttribute>(typeToIntercept));
            }

            // Cast out the return collection
            var returnMe = new Dictionary<string, IList<TAttribute>>();
            foreach (var kvp in FilterAttributesChainCache[typeof(TAttribute)][typeToIntercept])
            {
                returnMe[kvp.Key] = new List<TAttribute>(kvp.Value.Cast<TAttribute>());
            }

            return returnMe;
        }

        /// <summary>
        /// Builds an Attribute Chain
        /// </summary>
        /// <param name="type">
        /// The target type.
        /// </param>
        /// <typeparam name="TAttribute">
        /// The attribute type
        /// </typeparam>
        /// <returns>
        /// The attribute chain.
        /// </returns>
        private static Dictionary<string, List<PropertyInterceptionAttributeBase>> BuildAttributeChain<TAttribute>(Type type)
            where TAttribute : PropertyInterceptionAttributeBase
        {
            // Create the return collection
            // Filter chaining is from top->down in inheritance hierarchy, we must reverse the list.
            var customAttributes = type.FindAttributesDeepCheckProperties<TAttribute>();

            var returnMe = new Dictionary<string, List<PropertyInterceptionAttributeBase>>();
            foreach (var kvp in customAttributes)
            {
                returnMe[kvp.Key] = new List<PropertyInterceptionAttributeBase>(kvp.Value.Cast<PropertyInterceptionAttributeBase>());
            }

            return returnMe;
        }

        /// <summary>
        /// An attribute chain type declaration
        /// </summary>
        private class AttributeChainOnType : Dictionary<Type, Dictionary<string, List<PropertyInterceptionAttributeBase>>>
        {
        }
    }
}
